package com.cloudspace.yunloan.player.exo;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;

import com.cloudspace.yunloan.R;
import com.cloudspace.yunloan.player.base.AbsMediaPlayer;
import com.cloudspace.yunloan.utils.ToastMaker;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener;

/**
 * Created by liuhongtian on 16/10/31.
 */

public class ExoPlayerSystem extends AbsMediaPlayer
        implements ExoPlayer.EventListener, VideoRendererEventListener, AudioRendererEventListener {

    private final String SUB_TAG = "ExoPlayerSystem";
    private static final String USER_AGENT_APPLICATION_NAME = "easylive";
    private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
    private DataSource.Factory mediaDataSourceFactory;

    private SimpleExoPlayer mPlayer;
    private Uri mUri;

    private final static long DEFAULT_PROGRESS_ERROR_POSITION = -1202;
    private long mProgressOnError;// 失败时记录进度


    private Context mContext;

    public ExoPlayerSystem(Context context) {
        mContext = context;
        DefaultDataSourceFactory upstreamFactory = new DefaultDataSourceFactory(mContext, BANDWIDTH_METER, buildHttpDataSourceFactory(BANDWIDTH_METER));
        mediaDataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, ExoPlayerCacheController.getInstance().getDownloadCache());
        DefaultTrackSelector trackSelector = new DefaultTrackSelector(BANDWIDTH_METER);
        RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
        mPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
        mPlayer.setVideoDebugListener(this);
        mPlayer.setAudioDebugListener(this);
        mPlayer.addListener(this);
    }

    private void buildRenderer(Uri uri) {
        mUri = uri;
        // Produces Extractor instances for parsing the media data.
        // This is the MediaSource representing the media to be played.
        MediaSource mediaSource = buildMediaSource(uri);
        // Prepare the mPlayer with the source.
        mPlayer.prepare(mediaSource);
    }

    private MediaSource buildMediaSource(Uri uri) {
        @C.ContentType int type = Util.inferContentType(uri);
        switch (type) {
            case C.TYPE_HLS:
                return new HlsMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
            case C.TYPE_OTHER:
                return new ExtractorMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri);
            default: {
                throw new IllegalStateException("Unsupported type: " + type);
            }
        }
    }

    private HttpDataSource.Factory buildHttpDataSourceFactory(TransferListener<? super DataSource> listener) {
        String userAgent = Util.getUserAgent(mContext, USER_AGENT_APPLICATION_NAME);
        return new DefaultHttpDataSourceFactory(userAgent, listener);
    }

    private static CacheDataSourceFactory buildReadOnlyCacheDataSource(DefaultDataSourceFactory upstreamFactory, Cache cache) {
        return new CacheDataSourceFactory(
                cache,
                upstreamFactory,
                new FileDataSourceFactory(),
        /* cacheWriteDataSinkFactory= */ null,
                CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
        /* eventListener= */ null);
    }


    @Override
    public void setDataSource(String path) {
        Log.d(SUB_TAG, "setDataSource.");
        buildRenderer(Uri.parse(path));
        mPlayer.setPlayWhenReady(false);
    }

    @Override
    public void start() {
        Log.d(SUB_TAG, "start.");
        if (mProgressOnError != DEFAULT_PROGRESS_ERROR_POSITION
                && (mPlayer.getPlaybackState() == ExoPlayer.STATE_IDLE || mPlayState == STATE_ERROR_RETRY)
                && mUri != null) {
            buildRenderer(mUri);
            mPlayer.seekTo(mProgressOnError);
            mPlayer.setPlayWhenReady(true);
        } else {
            mPlayer.setPlayWhenReady(true);
        }
    }

    @Override
    public void pause() {
        Log.d(SUB_TAG, "pause.");
        mPlayer.setPlayWhenReady(false);
    }

    @Override
    public void prepare() {
        Log.d(SUB_TAG, "prepare.");
        mProgressOnError = DEFAULT_PROGRESS_ERROR_POSITION;
        mPlayer.addListener(this);
        mPlayer.setPlayWhenReady(true);
    }

    @Override
    public void prepareAsync() {
        Log.d(SUB_TAG, "prepareAsync.");
    }

    @Override
    public void stop() {
        Log.d(SUB_TAG, "stop.");
        if (mPlayer != null) {
            mPlayer.stop();
        }
    }

    @Override
    public void release() {
        if (mPlayer != null) {
            mPlayer.release();
        }
        mUri = null;
    }

    @Override
    public void reset() {
        seekTo(0);
        stop();
    }

    @Override
    public void seekTo(int msec) {
        Log.d(SUB_TAG, "seekTo.");
        if (mPlayer != null) {
            mPlayer.seekTo(msec);
        }
    }

    @Override
    public int getCurrentPosition() {
        return (int) mPlayer.getCurrentPosition();
    }

    @Override
    public int getDuration() {
        return (int) mPlayer.getDuration();
    }

    @Override
    public int getPlayerBufferedProgress() {
        return mPlayer.getBufferedPercentage();
    }

    @Override
    public void setDisplay(SurfaceHolder sh) {
        if (sh != null) {
            mPlayer.setVideoSurface(sh.getSurface());
        } else {
            clearSurface();
        }
    }


    private void clearSurface() {
        Log.d(SUB_TAG, "clearSurface.");
        mPlayer.clearVideoSurface();
    }

    @Override
    public boolean isPlaying() {
        int playbackState = mPlayer.getPlaybackState();
        return playbackState == Player.STATE_READY && mPlayer.getPlayWhenReady();
    }


    /**
     * implements ExoPlayer.EventListener
     */
    @Override
    public void onLoadingChanged(boolean isLoading) {
        // TODO
        Log.d(SUB_TAG, "ExoPlayerSystem onLoadingChanged");
    }

    /**
     * implements ExoPlayer.EventListener
     */
    @Override
    public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
        // TODO
        Log.d(SUB_TAG,
                "Exo playbackState = " + playbackState + " | playWhenReady = " + playWhenReady);

        switch (playbackState) {
            case Player.STATE_IDLE:
                mPlayState = STATE_IDLE;
                break;
            case Player.STATE_BUFFERING:
                mPlayState = STATE_PREPARING;
                break;
            case Player.STATE_READY:
                mPlayState = playWhenReady ? STATE_PLAYING : STATE_PREPARED;
                if (mOnPreparedListener != null) {
                    mOnPreparedListener.onPrepared(null);
                }
                break;
            case Player.STATE_ENDED:
                mPlayState = STATE_PLAYBACK_COMPLETED;
                break;
        }
        if (mOnPlayStateChangeListener != null) {
            mOnPlayStateChangeListener.onPlayStateChange(mPlayState);
        }

    }

    @Override
    public void onRepeatModeChanged(int repeatMode) {
        Log.d(SUB_TAG, "onRepeatModeChanged");
    }

    @Override
    public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
        Log.d(SUB_TAG, "onShuffleModeEnabledChanged");
    }

    /**
     * implements ExoPlayer.EventListener
     */
    @Override
    public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
        Log.d(SUB_TAG, "ExoPlayerSystem onTimelineChanged");
    }

    @Override
    public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
        Log.d(SUB_TAG, "onTracksChanged");
    }

    /**
     * implements ExoPlayer.EventListener
     */
    @SuppressLint("StringFormatInvalid")
    @Override
    public void onPlayerError(ExoPlaybackException error) {
        // TODO
        Log.d(SUB_TAG, "ExoPlayerSystem onLoadError | error : " + error.type);
        mProgressOnError = mPlayer.getCurrentPosition();
        mPlayState = error.type == ExoPlaybackException.TYPE_SOURCE ? STATE_ERROR_RETRY : STATE_ERROR;

        if (error.type == ExoPlaybackException.TYPE_RENDERER) {
            String errorString = null;
            Exception cause = error.getRendererException();
            if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
                // Special case for decoder initialization failures.
                MediaCodecRenderer.DecoderInitializationException decoderInitializationException = (MediaCodecRenderer.DecoderInitializationException) cause;
                Resources resource = mContext.getResources();
                if (decoderInitializationException.decoderName == null) {
                    if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
                        errorString = resource.getString(R.string.error_querying_decoders);
                    } else if (decoderInitializationException.secureDecoderRequired) {
                        errorString = resource.getString(R.string.error_no_secure_decoder,
                                decoderInitializationException.mimeType);
                    } else {
                        errorString = resource.getString(R.string.error_no_decoder,
                                decoderInitializationException.mimeType);
                    }
                } else {
                    errorString = resource.getString(R.string.error_instantiating_decoder,
                            decoderInitializationException.decoderName);
                }
            }
            if (errorString != null) {
                ToastMaker.showToastShort(errorString);
            }
        }
        if (mOnPlayStateChangeListener != null) {
            mOnPlayStateChangeListener.onPlayStateChange(mPlayState);
        }
    }

    /**
     * implements ExoPlayer.EventListener
     */
    @Override
    public void onPositionDiscontinuity(int reason) {
        Log.d(SUB_TAG, "onPositionDiscontinuity");
    }

    @Override
    public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
        Log.d(SUB_TAG, "onPlaybackParametersChanged");
    }

    @Override
    public void onSeekProcessed() {
        Log.d(SUB_TAG, "onSeekProcessed");
    }

    /**
     * implements VideoRendererEventListener
     */
    @Override
    public void onVideoEnabled(DecoderCounters counters) {
        Log.d(SUB_TAG, "onVideoEnabled");
    }

    /**
     * implements VideoRendererEventListener
     */
    @Override
    public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
                                          long initializationDurationMs) {
        Log.d(SUB_TAG, "onVideoDecoderInitialized");
    }

    /**
     * implements VideoRendererEventListener
     */
    @Override
    public void onVideoInputFormatChanged(Format format) {
        Log.d(SUB_TAG, "onVideoInputFormatChanged");
    }

    /**
     * implements VideoRendererEventListener
     */
    @Override
    public void onDroppedFrames(int count, long elapsedMs) {
        Log.d(SUB_TAG, "onDroppedFrames");
//        for (IMediaPlayerListener listener : mListeners) {
//            listener.onDroppedFrames(count, elapsedMs);
//        }
    }

    /**
     * implements VideoRendererEventListener
     */
    @Override
    public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
                                   float pixelWidthHeightRatio) {
        Log.d(SUB_TAG, "onVideoSizeChanged");
//        for (IMediaPlayerListener listener : mListeners) {
//            listener.onVideoSizeChanged(width, height, unappliedRotationDegrees,
//                    pixelWidthHeightRatio);
//        }
    }

    /**
     * implements VideoRendererEventListener
     */
    @Override
    public void onRenderedFirstFrame(Surface surface) {
        Log.d(SUB_TAG, "onRenderedFirstFrame");

    }

    /**
     * implements VideoRendererEventListener
     */
    @Override
    public void onVideoDisabled(DecoderCounters counters) {
        Log.d(SUB_TAG, "onVideoDisabled");
    }

    @Override
    public void onAudioEnabled(DecoderCounters counters) {
        Log.d(SUB_TAG, "onAudioEnabled");
    }

    @Override
    public void onAudioSessionId(int audioSessionId) {
        Log.d(SUB_TAG, "onAudioSessionId");
    }

    @Override
    public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {
        Log.d(SUB_TAG, "onAudioDecoderInitialized");
    }

    @Override
    public void onAudioInputFormatChanged(Format format) {
        Log.d(SUB_TAG, "onAudioInputFormatChanged");
    }

    @Override
    public void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
        Log.d(SUB_TAG, "onAudioSinkUnderrun");
    }

    @Override
    public void onAudioDisabled(DecoderCounters counters) {
        Log.d(SUB_TAG, "onAudioDisabled");
    }

}
